Repository navigation
Ask a few questions when a Magic Compose description doesn't say what people sign up for - #577
Conversation
The Magic Compose prompt now describes the questions reply and when to use it: only when the prompt doesn't say what people sign up for, listing only the topics it leaves out, with two to four options in the prompt's language and never a date or a count, since the app asks for those itself. A message that carries the organizer's answers is never answered with questions. The drafter's never-invent rule splits in two. No "what" asks; a prompt that says what but not when or how many still drafts "TBD:" rows, which the builder's placeholder checks rely on. Example C, the vague soccer prompt, becomes a questions reply, and a new Example D keeps the placeholder pattern for "Snack duty for the soccer team, two families per game". The prompt also says capacity may be null, which is where the follow-up's "no limit" lands. RULES_IN_BOTH and the assistant's rule are untouched, so the MCP server instructions don't change. The golden file is regenerated. A new test parses every worked example with the server's schema, and others hold the prompt to the follow-up message's label and its "no limit". Part of #570. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
The eval script treated a questions reply as a failed call, so nothing measured whether the prompt asks when it should. The harness gains a questions arm: a case with questions.* checks expects one (a draft fails as expected_questions_but_drafted), and questions anywhere else fail as unexpected_questions, as a stray refusal does. The checks cover the topics asked (include, exclude, equal), the number of options, options holding a date or a number, and whole-word matches on the options, which the French case uses to check they are in French. A case with `answers` runs both calls the page makes: the prompt, which must ask, then the prompt with the answers through FollowUpSchema and buildFollowUpMessages, whose draft the existing checks score. Questions there fail as asked_again. It is a new key because followUps already means organizer turns in the MCP runner. ev_008 now expects questions instead of 1-6 placeholder slots, and ten made-up cases cover name-only prompts, a date with no "what", a French prompt, near misses that must still draft (with "TBD:" rows where the slots need dates), and drafts from answers and from Draft with placeholders. pnpm eval:mcp reads the same file. An assistant that asks the organizer is right where Magic Compose would ask, so there a reply passes as asked_before_creating; "assistant_may_ask" marks the two placeholder cases where an assistant may ask for the dates instead; cases with answers are skipped. Every existing case scores as before. Part of #570. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Both calls of a questions round reach the AI, so both count: a description that gets questions now uses two drafts. Two more an hour make up for a couple of those. The route's comment no longer names the number, so it can't go stale again. Part of #570. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
When the description doesn't say what people sign up for, the organizer now answers a few questions and the answers are sent to the AI provider with it. The privacy page's Third parties paragraph says so; it still names nothing else about the organizer and nothing about participants. The Last updated date is already today's. The CHANGELOG gains the line for the questions round being on: what asks and what still drafts, the quota going to 12 an hour, and the evals, with ev_008 now expecting questions. Part of #570. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
…al from copying an example
Rule 4 told the drafter to ask whenever a prompt doesn't say what people
sign up for, with the exception only in the QUESTIONS section. Draft with
placeholders sends exactly that ("What people sign up for: not given"),
so it was the follow-up most likely to be asked again. The rule now says
"and carries no answers to your questions" itself.
Example D's prompt was word for word ev_033's, so that case measured
copying the example rather than the rule. Example D is now school play
ushers, two parents per performance; ev_033 keeps the design's snack duty
prompt. Golden file regenerated; the diff is these two changes.
Part of #570.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
The review of the eval harness found it disagreeing with the route and the page in a few places, so its scores could mislead. A reply to the answers that asks again with only part of a question (just "when", say) was scored as a broken draft. It is now asked_again, read with asksQuestions as the route reads it. The second call now sends "when" and "how many" only when the first call asked about them, as the page does; when the first call drafted instead, the answers still go back as written. A case's answers are checked with FollowUpSchema before any paid call, and a bad one is reported as a broken case rather than an LLM failure. When the second call fails, its error says "second call:" and the first call's checks are still printed. "questions.must_ask": false now expects a draft instead of counting as a questions case. In eval:mcp a reply passes an ask case only when it asks something (holds a "?"), so a refusal or an empty reply fails. French number words join the date-or-number check for the French case. asksFirst loses a branch nothing could reach. The harness gets its own unit tests, so vitest now also runs scripts/**/*.test.ts. The changed files are run through prettier. Part of #570. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
#526 added its own ev_027 (the classroom starter), so this branch's new cases are renumbered ev_028 to ev_037; the README follows. The prompt and its golden file merge cleanly: #526's CAPACITY section and this branch's QUESTIONS section both render, and the golden-file test passes. The quota line keeps 12 beside #525's link-events limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Through the server, telemetry and screen branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
…he 5-prompt branch Through the server, telemetry and screen branches. rate-limit.ts keeps this branch's 12 drafts an hour and main's reworded link-beacon comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Through the server, telemetry and screen branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Through the server, telemetry and screen branches. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Tophat and evals: the prompt that asksThe app. I ran The server sends the merged system prompt, with both QUESTIONS and CAPACITY. The follow-up message matches #573's format word for word. The server log holds no description or answer text. Borderline prompts on Flash (made up, 3 tries each):
The evals you asked for. Flash ran 3× per side. A panel of 9 OpenRouter models also ran 3× per side: llama-3.3-70b, llama-3.1-8b, mistral-small-3.2, qwen3-30b-a3b, gemma-3-27b, gpt-oss-120b, deepseek-v4-flash, gpt-4o-mini and claude-haiku-5.5. The comparison is The three cases you asked to watch pass on Flash, 4 of 4 each:
Good
To fix
Your call
Weak spots, for the record:
Related, older than this stack: one-day drafts keep the date in the description and get no Date column, so their reminders never go out. With the plain description it happened 6 of 6 times on Flash. After this stack's "One day" answer it happened 3 of 8 times. Filed as #685, to follow this PR. The evals made 2,239 calls for $3.40. Generated by Claude Code |
…ty for "no limit" An eval run over many models found the questions round asking about prompts that already say what people sign up for: a supplies list got questions whose options were the list's own items. Rule 4 now says that a list of items, roles, tasks or dishes, or a schedule of dated sessions, practices or shifts, already says what, so the drafter drafts. The QUESTIONS section points back to rule 4, and an option that would repeat something the prompt lists is the sign to draft instead. Example E shows an item list with no date or count drafted as it stands: no date field, no placeholders, capacity 1. The same run found null capacities on drafts whose prompts never said "no limit", traced to the schema line offering null beside the default. The line and rule 9 now keep null for a prompt or answers that say "no limit", which the follow-up message still sends for an empty count box, and say a count that isn't given is 1. Golden file rewritten from the new prompt; its diff is these changes. The MCP instructions and RULES_IN_BOTH are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
…ample C
ev_008 was Example C's prompt word for word, so it measured whether a
model recalls the example: in the owner's eval run most replies that
asked copied Example C's options. It is now a different vague signup
with no "what" (a swim club), with the same expectations, plus a check
that its options aren't the soccer team's ("away games" don't fit a swim
club).
A test in prompt.test.ts now fails when a worked example's prompt is, or
holds, an eval case's, and the evals README says why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
The owner's eval run turned up checks that failed good replies or passed
bad ones:
- slot_dates.* read a "TBD: date 1" placeholder in a date field as a
date, so ev_037 failed a draft that invented nothing. A TBD value is
now no date, as it is to the converter.
- eval:mcp passed an assistant that asked instead of creating only when
its reply held a "?", and failed a real request phrased without one.
The check now lives in the harness (askedBeforeCreating) and also
takes a short list of requests for details ("Let me know the dates",
"Please share...", "I'll need the dates"). An empty reply still
fails, and so does one that asks for nothing, such as a refusal that
ends "Let me know if there's anything else".
- ev_031 accepted any option holding a French article or a verb that
fits any signup, so off-topic French passed. It now needs a word
about the bake sale itself (cakes, the sale, its stand or drinks).
- ev_002 rejected a number field named "quantity" (or "amount"), a
reasonable way to give 80 cookies, 40 brownies and 30 cupcakes.
Each fix has a test in eval-harness.test.ts, the case fixes against the
cases as they stand in dev-evals.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
An advisor read of the new prompt found two risks for small models. Rule 4's new sentence said a schedule of dated sessions already says what, so a lone dated event such as "Team party on Saturday, June 5" could now draft; it now names an event with no item or role as the case that still asks. And "if an option would repeat something the prompt lists" could read as "Bake sale" → "Bake something to sell", which the questions evals need; it now speaks of the prompt's own items or roles. Rule 9 says the same as before in fewer words. The golden file is rewritten to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
A review of the prompt and eval fixes found: - ev_031's word list counted "stand", "table", "biscuits" and "crepes", which are English too, so English options passed the French case. - The asked-first check passed a refusal that closed with "Let me know what else I can help with" or "Please tell me if you'd like…". - slot_dates counted any date value but a TBD one, though the converter keeps only real days, so a weekday or "TBD (2026-06-06)" in a date field was scored as a date. It now uses the converter's isRealDate. - ev_034's prompt held rule 4's own e.g., "snack duty for the soccer team", as ev_008 held Example C. It is now the basketball team, and the guard test checks the rules' e.g. snippets too, word by word. - Quoting "no limit" in the schema line and rule 9 could read as the exact words only, so "unlimited" or "as many as want" would get capacity 1. Both now say "there's no limit", with those as examples. The golden file is rewritten to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Brings in the questions screen's review fixes, the first call's retry for questions of the wrong shape and the privacy page's date. The CHANGELOG keeps this branch's line, now saying what counts as saying what, the first reply's retry and when a slot has no limit, beside the telemetry line's updated placeholders meaning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Through the server, telemetry and screen branches. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
|
Thanks for the eval run. It found what my fake endpoint couldn't. Everything below is pushed. I can't run a real model here, so the new wording needs another eval run before merging. To fix
Your call
Worth watching in the next run
#685 is noted as the follow-up. Generated by Claude Code |
Fixes #570, the last of five stacked PRs. It's based on #576 (the questions screen). This is the PR that turns the questions on. Merge it together with #576.
The problem
A Magic Compose description that's only an event's name, like "Bake sale", gives the model nothing to build slots from. It either makes three "TBD" rows or invents jobs and counts, and the organizer still uses one of the hour's drafts.
The fix
The prompt
Rule 4 (the drafter's never-invent text,
NEVER_INVENT_MIDDLE.drafterinsignup-rules.ts) now splits the cases:RULES_IN_BOTHand the AI connector's instructions are unchanged, and their tests prove it.A QUESTIONS section after the output schema gives the reply shape and its rules. It applies only when the prompt doesn't say what people sign up for (rule 4):
Capacity is
<integer, 1 by default; null only if the prompt or answers say there's no limit>. Rule 9 ends:No limit ("unlimited", "as many as want") means null; never null as a guess.The follow-up message's "no limit" relies on null.The worked examples:
{"questions":{"topics":["what","when","how_many"],"whatOptions":["Bring snacks","Drive to away games","Help at practice"]}}.TBD: performance 1–3with 2 spots each. Find fixable problems in a draft and offer one-tap fixes #503 relies on those "TBD:" rows.The golden file is regenerated; its diff is only these changes. A test parses every worked example with the server's schema, and another fails when a worked example or a rule's "e.g." matches an eval prompt, whole words only, so no case scores recall of the prompt's own words.
Evals
The harness (
scripts/lib/eval-harness.ts,scripts/eval-magic-compose.ts):questionsresult arm.unexpected_questions;expected_questions_but_drafted;asked_again: questions in reply to the answers, in any shape, read with the route's ownasksQuestions.questions.must_ask(true or false),topics_must_include,topics_must_not_include,topics_must_equal,options_count_range,options_must_not_contain_date_or_number(English and French number words, months and weekdays),options_must_mention_anyandoptions_must_not_mention_any.slot_dates.*counts only real days, with the converter's ownisRealDate, so a "TBD: date 1" placeholder or a weekday in a date field isn't a date.answers(aFollowUp) is checked withFollowUpSchemabefore any model call. The first call must ask. Then the answers go throughbuildFollowUpMessages, as in the route, and the draft is scored.whenonly if the first call asked "when",howManyonly if it asked "how_many".answersis a new key becausefollowUpsalready means the MCP runner's organizer turns.scripts/lib/eval-harness.test.tstests the harness's own logic. For it to run,vitest.config.tsnow includesscripts/**/*.test.ts.pnpm eval:mcploads the same file:asked_before_creating: with a "?", or a request for details in so many words ("Please share the dates…", "I'll need…"). An empty reply, a refusal (even one closing "Let me know what else…") and a create fail.answerscases are skipped.Cases. #526 added
ev_027(the classroom starter), so this PR's new cases areev_028–ev_037.ev_008flips from "drafts 1–6 slots" to "asks", with a swim club prompt of its own (it used to be Example C word for word).ev_002accepts aquantityoramountfield.ev_028ev_029ev_030ev_031ev_032ev_033ev_034ev_035ev_036nullfor no limitev_037Quota
magicComposePerOrganizergoes from 10 to 12 an hour. A thin description now takes two AI calls: the first, and the one with the answers. The free retries don't count. No docs or pages named the old number.Privacy and changelog
The privacy page already says the answers go to the AI provider (#573). CHANGELOG has a line under Added.
Your two open questions
I took the issue's defaults:
Evals
The owner's run over Flash and a panel of nine models (in the comments) found the prompt asked about prompts that already list what people sign up for,
nullcapacity where nobody said "no limit", ev_008 copying Example C, and four harness bugs. All are fixed above. The new wording hasn't met a real model yet (this session has none), so please run again, on this branch and onmain:Watch:
Reviews
/code-review), fixed in 7b78492. It found eight harness issues:must_ask: falsemeant the opposite;/code-reviewof that, fixed in 82c6a92: ev_031 counted words English shares, the MCP check passed a refusal's closing line,slot_datescounted non-dates, ev_034 held rule 4's "e.g.", and quoting "no limit" could read as the exact words only.eval-harness.ts,eval-mcp.tsand the README, files this PR already rewrites.Tophat
On this branch, against the fake endpoint:
Tests
pnpm lint && pnpm typecheck && pnpm knip && pnpm test(1988) andpnpm test:db(623) pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks